using System;
using System.ComponentModel;
using System.Collections;
using System.Globalization;
using System.Collections.Generic;

namespace DataBroker.Dao
{
    /// <summary>
    /// The class EntityCollection .
    /// </summary>
    /// <summary>
    /// We use array list so we dont have to write all those
    /// boring methods ( Count, Add(), RemoveAt() ...)
    /// </summary>
    [Serializable]
    public sealed class EntityCollection : List<Entity>, IBindingList , ITypedList
    {
        private object _instance;
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="arrayType">Type of element item in the collection.</param>
        public EntityCollection(Type arrayType)
        {
            // discover the type inside the collections - this is jsut for fun.
            // could have been passed on the constructor
            foreach ( Attribute a in this.GetType().GetCustomAttributes(typeof(TypedCollectionAttribute),false) )
            {
                this.finalType = ((TypedCollectionAttribute)a).CollectionType;
                break;
            }
            finalType = arrayType;
            if ( this.finalType == null )
                throw new Exception("Must supply the type as a TypedCollectionAttibute of the class");
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="instance">Instance of object to create when cloning.</param>
        public EntityCollection (object instance) : this(instance != null ? instance.GetType() : null)
        {
            _instance = instance;
        }

        private Type finalType; 

        /// <summary>
        /// The type inside the collection
        /// </summary>
        public Type ElementType
        {
            get { return this.finalType; }
        }

        /// <summary>
        /// The instance inside the collection
        /// </summary>
        public object ElementInstance
        {
            get
            {
                return _instance;
            }
        }
        #region IBindingList

        private ListChangedEventHandler onListChanged;

        /// <summary>
        /// Event fired when list changes.
        /// </summary>
        /// <param name="ev">Event.</param>
        private void OnListChanged(ListChangedEventArgs ev) 
        {
            try
            {
                onListChanged(this, ev);
            }
            finally 
            {
            }
        }

        /// <summary>
        /// Event Handler for when the List changes.
        /// </summary>
        event ListChangedEventHandler IBindingList.ListChanged 
        {
            add 
            {
                onListChanged += value;
            }
            remove 
            {
                onListChanged -= value;
            }
        }
		
        /// <summary>
        /// List allow editing.
        /// </summary>
        bool IBindingList.AllowEdit 
        { 
            get { return true ; }
        }

        /// <summary>
        /// List allows new values.
        /// </summary>
        bool IBindingList.AllowNew 
        { 
            get { return true ; }
        }

        /// <summary>
        /// List allows value removal.
        /// </summary>
        bool IBindingList.AllowRemove 
        { 
            get { return true ; }
        }

        /// <summary>
        /// This List does not support change notification
        /// </summary>
        bool IBindingList.SupportsChangeNotification 
        { 
            get { return false ; }
        }
        
        /// <summary>
        /// List supports searching
        /// </summary>
        bool IBindingList.SupportsSearching 
        { 
            get { return true ; }
        }

        /// <summary>
        /// List supports Sorting
        /// </summary>
        bool IBindingList.SupportsSorting 
        { 
            get { return true ; }
        }

        /// <summary>
        /// Add new item 
        /// </summary>
        /// <returns>null</returns>
        object IBindingList.AddNew() 
        {
            return null;
        }

        private bool isSorted;

        /// <summary>
        /// Returns sorted status.
        /// </summary>
        bool IBindingList.IsSorted 
        { 
            get { return isSorted; }
        }

        private ListSortDirection listSortDirection = ListSortDirection.Ascending;
		
        ListSortDirection IBindingList.SortDirection 
        { 
            get { return listSortDirection; }
        }

        [NonSerialized()]
        PropertyDescriptor sortProperty;

        PropertyDescriptor IBindingList.SortProperty 
        { 
            get { return sortProperty; }
        }

        void IBindingList.AddIndex(PropertyDescriptor property) 
        {
            isSorted = true;
            sortProperty = property;
        }

        void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction) 
        {
            isSorted = true;
            sortProperty = property;
            listSortDirection = direction;
            this.Sort( new ObjectPropertyComparer(property.Name));
            if (direction == ListSortDirection.Descending) this.Reverse();
        }

        int IBindingList.Find(PropertyDescriptor property, object key) 
        {
            foreach( Entity o in this)
            {
                if ( Match( finalType.GetProperty(property.Name).GetValue(o,null) , key) ) 
                    return this.IndexOf(o);
            }
            return -1;
        }

        void IBindingList.RemoveIndex(PropertyDescriptor property) 
        {
            sortProperty = null;
        }

        void IBindingList.RemoveSort() 
        {
            isSorted = false;
            sortProperty = null;
        }

        #endregion

        #region ITypedList
		
		
        /// <summary>
        /// Creates the actual property descriptor collection for the type passed in.
        /// </summary>
        /// <param name="typeOfObject">the type of the object to get the property descriptors for</param>
        /// <returns>filled property descriptor collection</returns>
        /// <remarks>It will skip every property which has the HiddenForDataBindingAttribute attribute
        /// applied to it with a value of true.</remarks>
        private static PropertyDescriptorCollection GetPropertyDescriptors(Type typeOfObject) 
        {
            PropertyDescriptorCollection typePropertiesCollection = TypeDescriptor.GetProperties(typeOfObject);
            ArrayList propertyDescriptorsToUse = new ArrayList();
            // now walk all properties in the property descriptor collection. If the property has the 
            // HiddenForDataBindingAttribute applied to it, it should be skipped.
            foreach(PropertyDescriptor property in typePropertiesCollection)
            {
                HiddenForDataBindingAttribute hiddenAttribute = 
                    (HiddenForDataBindingAttribute)property.Attributes[typeof(HiddenForDataBindingAttribute)];
                if(hiddenAttribute!=null)
                {
                    // check if the value is false.
                    if(hiddenAttribute.IsHidden)
                    {
                        // skip
                        continue;
                    }
                }
                // add it, as it doesn't have the attribute applied to it Or the attribute's value is false
                propertyDescriptorsToUse.Add(property);
            }
            return new PropertyDescriptorCollection((PropertyDescriptor[])propertyDescriptorsToUse.ToArray(typeof(PropertyDescriptor))); 
        }

        /// <summary>
        /// Get the properties for an item, based on the passed list of accessors.
        /// </summary>
        /// <param name="listAccessors">List of accessors</param>
        /// <returns>Collection of properties.</returns>
        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
        {
            Type typeOfObject = null;

            // determine the type of the object to return the properties of. We first have to check
            // whether the properties requested are for an object in this instance (listAccessors==null or empty), or
            // that we're called to supply the property descriptors for an instance deeper in the object graph we're the
            // root of.
            if((listAccessors==null)||listAccessors.Length==0)
            {
                // provide the property descriptors for objects of this instance.
                typeOfObject = finalType;
            }
            else
            {
                // use the last entry in the listAccessors, grab its TypeContainedAttribute and instantiate an instance of 
                // the type in that attribute, use that entity instance to produce properties.
                TypeContainedAttribute typeAttribute = 
                    (TypeContainedAttribute)listAccessors[listAccessors.Length-1].Attributes[typeof(TypeContainedAttribute)];
			
                if(typeAttribute==null)
                {
                    // not found, not specified, can't determine properties.
                    return new PropertyDescriptorCollection(null);
                }

                typeOfObject = typeAttribute.TypeContainedInCollection;
            }
			
            // create property descriptors.
            return GetPropertyDescriptors(typeOfObject);
        }

		
		
        string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
        {
            string name = "";

            if ( listAccessors != null )
            {
                TypeContainedAttribute typeAttribute = 
                    (TypeContainedAttribute)listAccessors[listAccessors.Length-1].Attributes[typeof(TypeContainedAttribute)];
                if (typeAttribute != null)
                    return typeAttribute.TypeContainedInCollection.Name;
            }
            else
                name = this._instance.GetType().Name;

            return name;
        }
        #endregion

        #region Helper functions

        /// <summary>
        /// Get collection of property descriptors.
        /// </summary>
        /// <param name="properties">Arrray of properties</param>
        /// <returns>Collection of property descriptors.</returns>
        private static PropertyDescriptorCollection GetPropertyDescriptorCollection( ArrayList properties )
        {
            if ( properties == null || properties.Count == 0 )
                return new PropertyDescriptorCollection(null);

            ArrayList output = new ArrayList();

            foreach ( PropertyDescriptor p in properties )
            {
                if ( p.Attributes.Matches(new Attribute[]{new BindableAttribute(false)}) ) continue;
                EntityPropertyDescriptor epd = new EntityPropertyDescriptor(p.Name, p.ComponentType.GetProperty(p.Name));
                output.Add(epd);
            }
            return new PropertyDescriptorCollection((PropertyDescriptor[])output.ToArray(typeof(PropertyDescriptor)));
        }

        /// <summary>
        /// See if two objects have the same contents.
        /// </summary>
        /// <param name="data">Object to compare</param>
        /// <param name="searchValue">Value to compare to.</param>
        /// <returns></returns>
        private static bool Match( object data, object searchValue )
        {
            // handle nulls
            if ( data == null || searchValue == null )
            {
                return (bool)(data == searchValue);
            }

            // if its a string, our comparisons should be 
            // case insensitive.
            bool IsString = (bool)(data is string);
            Type dataType = data.GetType();

            // bit of validation b4 we start...
            if ( dataType != searchValue.GetType() )
                throw new ArgumentException("Objects must be of the same type");

            if ( ! (dataType.IsValueType || IsString ) )
                throw new ArgumentException("Objects must be a value type");

			

            /*
			 * Less than zero a is less than b. 
			 * Zero a equals b. 
			 * Greater than zero a is greater than b. 
			 */

            if ( IsString )
            {
                return (bool)(String.Compare((string)data,(string)searchValue, true,CultureInfo.CurrentCulture) == 0);			
            }		
            else
            {
                return (bool)(Comparer.Default.Compare(data,searchValue) == 0 );			
            }
        }

        #endregion


        /// <summary>
        /// Provide the strongly typed member for ICollection.
        /// </summary>
        /// <param name="array"></param>
        /// <param name="index"></param>
        public new void CopyTo(Entity[] array, int index)
        {
            ((ICollection)this).CopyTo(array, index);
        }

    }
}